﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework;
using FarseerGames.FarseerPhysics.Collisions;
using FarseerGames.FarseerPhysics.Factories;
using FarseerGames.FarseerPhysics.Dynamics;
using LaughingDog.Managers;
using LaughingDog.GameObjects;
using LaughingDog.Components;

namespace LaughingDog.Input
{
    /// <summary>
    /// Enumeration of all Mouse buttons.
    /// </summary>
    public enum MouseButtons { LeftButton, RightButton, MiddleButton, XButton1, XButton2 }

    /// <summary>
    /// Enumeration of all Mouse button event states.
    /// </summary>
    public enum MouseButtonStates { Released, JustReleased, Pressed, JustPressed }

    /// <summary>
    /// Object used to access all mouse functions.
    /// </summary>
    public class Mouse : InputDevice
    {        
        private MouseState mCurrentState = new MouseState();
        private MouseState mPreviousState = new MouseState();
        private Dictionary<MouseButtons, MouseButtonStates> mButtonStates = new Dictionary<MouseButtons, MouseButtonStates>();

        private Body mMouseBody;
        private Geom mMouseGeom;

        /// <summary>
        /// The current scroll wheel value of the Mouse.
        /// </summary>
        public Int32 ScrollWheel { get { return mCurrentState.ScrollWheelValue; } }

        /// <summary>
        /// The current position of the Mouse in screen coordinates.
        /// </summary>
        public Vector2 Position { get { return new Vector2(mCurrentState.X, mCurrentState.Y); } }

        /// <summary>
        /// The current X position of the Mouse in screen coordinates.
        /// </summary>
        public Int32 X { get { return (Int32)Position.X; } }

        /// <summary>
        /// The current Y position of the Mouse in screen coordinates.
        /// </summary>
        public Int32 Y { get { return (Int32)Position.Y; } }

        /// <summary>
        /// The current state of the LeftButton.
        /// </summary>
        public MouseButtonStates LeftButton { get { return GetButtonState(MouseButtons.LeftButton); } }

        /// <summary>
        /// The current state of the MiddleButton.
        /// </summary>
        public MouseButtonStates MiddleButton { get { return GetButtonState(MouseButtons.MiddleButton); } }

        /// <summary>
        /// The current state of the RightButton.
        /// </summary>
        public MouseButtonStates RightButton { get { return GetButtonState(MouseButtons.RightButton); } }

        /// <summary>
        /// The current state of the XButton1.
        /// </summary>
        public MouseButtonStates XButton1 { get { return GetButtonState(MouseButtons.XButton1); } }

        /// <summary>
        /// The current state of the XButton2.
        /// </summary>
        public MouseButtonStates XButton2 { get { return GetButtonState(MouseButtons.XButton2); } }

        /// <summary>
        /// Shows or hides the mouse cursor.
        /// </summary>
        public Boolean ShowCursor { get { return Engine.Game.IsMouseVisible; } set { Engine.Game.IsMouseVisible = value; } }

        /// <summary>
        /// Gets the Geometry object attached to the mouse.
        /// </summary>
        public Geom MouseGeometry { get { return mMouseGeom; } }

        /// <summary>
        /// Creates a new instance of a Mouse device. One is automatically created in the InputManager and can be accessed via InputManager.Instance.Mouse.
        /// </summary>
        public Mouse()
        {
            foreach (MouseButtons button in Enum.GetValues(typeof(MouseButtons)))
                mButtonStates.Add(button, MouseButtonStates.Released);

            mMouseBody = BodyFactory.Instance.CreateCircleBody(PhysicsManager.Instance.Simulator, 3, 1);
            mMouseGeom = GeomFactory.Instance.CreateCircleGeom(PhysicsManager.Instance.Simulator, mMouseBody, 3, 3);
            mMouseGeom.CollisionResponseEnabled = false;            
        }

        /// <summary>
        /// Updates this Mouse device.
        /// </summary>
        public override void Update()
        {
            mCurrentState = Microsoft.Xna.Framework.Input.Mouse.GetState();

            #region LeftButton

            if (mCurrentState.LeftButton == ButtonState.Pressed
                && mPreviousState.LeftButton == ButtonState.Released) // Down this frame, not the last
            {
                mButtonStates[MouseButtons.LeftButton] = MouseButtonStates.JustPressed;
            }
            else if (mCurrentState.LeftButton == ButtonState.Pressed
                && mPreviousState.LeftButton == ButtonState.Pressed) // Down this frame and the last
            {
                mButtonStates[MouseButtons.LeftButton] = MouseButtonStates.Pressed;
            }
            else if (mCurrentState.LeftButton == ButtonState.Released
                && mPreviousState.LeftButton == ButtonState.Pressed) // Up this frame, not the last
            {
                mButtonStates[MouseButtons.LeftButton] = MouseButtonStates.JustReleased;
            }
            else if (mCurrentState.LeftButton == ButtonState.Released
                && mPreviousState.LeftButton == ButtonState.Released) // Up this frame and the last
            {
                mButtonStates[MouseButtons.LeftButton] = MouseButtonStates.Released;
            }

            #endregion

            #region MiddleButton

            if (mCurrentState.MiddleButton == ButtonState.Pressed
                && mPreviousState.MiddleButton == ButtonState.Released) // Down this frame, not the last
            {
                mButtonStates[MouseButtons.MiddleButton] = MouseButtonStates.JustPressed;
            }
            else if (mCurrentState.MiddleButton == ButtonState.Pressed
                && mPreviousState.MiddleButton == ButtonState.Pressed) // Down this frame and the last
            {
                mButtonStates[MouseButtons.MiddleButton] = MouseButtonStates.Pressed;
            }
            else if (mCurrentState.MiddleButton == ButtonState.Released
                && mPreviousState.MiddleButton == ButtonState.Pressed) // Up this frame, not the last
            {
                mButtonStates[MouseButtons.MiddleButton] = MouseButtonStates.JustReleased;
            }
            else if (mCurrentState.MiddleButton == ButtonState.Released
                && mPreviousState.MiddleButton == ButtonState.Released) // Up this frame and the last
            {
                mButtonStates[MouseButtons.MiddleButton] = MouseButtonStates.Released;
            }

            #endregion

            #region RightButton

            if (mCurrentState.RightButton == ButtonState.Pressed
                && mPreviousState.RightButton == ButtonState.Released) // Down this frame, not the last
            {
                mButtonStates[MouseButtons.RightButton] = MouseButtonStates.JustPressed;
            }
            else if (mCurrentState.RightButton == ButtonState.Pressed
                && mPreviousState.RightButton == ButtonState.Pressed) // Down this frame and the last
            {
                mButtonStates[MouseButtons.RightButton] = MouseButtonStates.Pressed;
            }
            else if (mCurrentState.RightButton == ButtonState.Released
                && mPreviousState.RightButton == ButtonState.Pressed) // Up this frame, not the last
            {
                mButtonStates[MouseButtons.RightButton] = MouseButtonStates.JustReleased;
            }
            else if (mCurrentState.RightButton == ButtonState.Released
                && mPreviousState.RightButton == ButtonState.Released) // Up this frame and the last
            {
                mButtonStates[MouseButtons.RightButton] = MouseButtonStates.Released;
            }

            #endregion

            #region XButton1

            if (mCurrentState.XButton1 == ButtonState.Pressed
                && mPreviousState.XButton1 == ButtonState.Released) // Down this frame, not the last
            {
                mButtonStates[MouseButtons.XButton1] = MouseButtonStates.JustPressed;
            }
            else if (mCurrentState.XButton1 == ButtonState.Pressed
                && mPreviousState.XButton1 == ButtonState.Pressed) // Down this frame and the last
            {
                mButtonStates[MouseButtons.XButton1] = MouseButtonStates.Pressed;
            }
            else if (mCurrentState.XButton1 == ButtonState.Released
                && mPreviousState.XButton1 == ButtonState.Pressed) // Up this frame, not the last
            {
                mButtonStates[MouseButtons.XButton1] = MouseButtonStates.JustReleased;
            }
            else if (mCurrentState.XButton1 == ButtonState.Released
                && mPreviousState.XButton1 == ButtonState.Released) // Up this frame and the last
            {
                mButtonStates[MouseButtons.XButton1] = MouseButtonStates.Released;
            }

            #endregion

            #region XButton2

            if (mCurrentState.XButton2 == ButtonState.Pressed
                && mPreviousState.XButton2 == ButtonState.Released) // Down this frame, not the last
            {
                mButtonStates[MouseButtons.XButton2] = MouseButtonStates.JustPressed;
            }
            else if (mCurrentState.XButton2 == ButtonState.Pressed
                && mPreviousState.XButton2 == ButtonState.Pressed) // Down this frame and the last
            {
                mButtonStates[MouseButtons.XButton2] = MouseButtonStates.Pressed;
            }
            else if (mCurrentState.XButton2 == ButtonState.Released
                && mPreviousState.XButton2 == ButtonState.Pressed) // Up this frame, not the last
            {
                mButtonStates[MouseButtons.XButton2] = MouseButtonStates.JustReleased;
            }
            else if (mCurrentState.XButton2 == ButtonState.Released
                && mPreviousState.XButton2 == ButtonState.Released) // Up this frame and the last
            {
                mButtonStates[MouseButtons.XButton2] = MouseButtonStates.Released;
            }

            #endregion

            foreach (GameObject gameObject in new List<GameObject>(GameObjectManager.Instance.Objects))
            {                
                // This whole loop is a monster and seriously needs to be sorted out. 
                // Perhaps moving things out to individual gameobjects checking for collision with mouse 
                // rather than the other way around.
                if (gameObject.Collider != null)
                {                    
                    if (gameObject.Collider.Geom.Collide(mMouseGeom))
                    {
                        if (gameObject.OnMouseHovering != null) gameObject.OnMouseHovering();
                        
                        gameObject.MouseHovering();
       
                        foreach (MouseButtons button in Enum.GetValues(typeof(MouseButtons)))
                        {
                            if (InputManager.Instance.Mouse.ButtonJustPressed(button))
                            {
                                if (gameObject.OnMouseJustPressed != null) gameObject.OnMouseJustPressed(button);

                                gameObject.MouseJustPressed(button);                                              
                            }
                            else if (InputManager.Instance.Mouse.ButtonPressed(button))
                            {
                                if (gameObject.OnMousePressed != null) gameObject.OnMousePressed(button);

                                gameObject.MousePressed(button);                                
                            }
                            else if (InputManager.Instance.Mouse.ButtonJustReleased(button))
                            {
                                if (gameObject.OnMouseJustReleased != null) gameObject.OnMouseJustReleased(button);

                                gameObject.MouseJustReleased(button);                                          
                            }
                        }                        
                    }
                }
            }

            mPreviousState = mCurrentState;

            mMouseBody.Position = Position;

            base.Update();
        }

        /// <summary>
        /// Returns whether a mouse button is currently pressed. Down last frame and this one.
        /// </summary>
        /// <param name="button">The mouse button to check.</param>
        /// <returns>True if the button is currenty pressed, false if not.</returns>
        public Boolean ButtonPressed(MouseButtons button)
        {
            return IsButtonInState(button, MouseButtonStates.Pressed);
        }

        /// <summary>
        /// Returns whether a mouse button has just been pressed. Down this frame but up during last one.
        /// </summary>
        /// <param name="button">The mouse button to check.</param>
        /// <returns>True if the button was just pressed, false if not.</returns>
        public Boolean ButtonJustPressed(MouseButtons button)
        {
            return IsButtonInState(button, MouseButtonStates.JustPressed);
        }

        /// <summary>
        /// Returns whether a mouse button has just been released. Up this frame but down during last one.
        /// </summary>
        /// <param name="button">The mouse button to check.</param>
        /// <returns>True if the button was just released, false if not.</returns>
        public Boolean ButtonJustReleased(MouseButtons button)
        {
            return IsButtonInState(button, MouseButtonStates.JustReleased);
        }

        /// <summary>
        /// Returns whether a mouse button is currently released. Up last frame and this one.
        /// </summary>
        /// <param name="button">The mouse button to check.</param>
        /// <returns>True if the button is currently released, false if not.</returns>
        public Boolean ButtonReleased(MouseButtons button)
        {
            return IsButtonInState(button, MouseButtonStates.Released);
        }

        /// <summary>
        /// Returns whether a mouse button was just double clicked.
        /// </summary>
        /// <param name="button">The mouse button to check.</param>
        /// <returns>True if the button was just double clicked, false if not.</returns>
        public Boolean ButtonDoubleClicked(MouseButtons button)
        {
            throw new NotImplementedException("DoubleClicking is not yet implemented, you will have to work this out yourself for now. Sorry.");
        }

        /// <summary>
        /// Checks if a mouse button is in a certain state.
        /// </summary>
        /// <param name="button">The mouse button to check.</param>
        /// <param name="state">The state to check.</param>
        /// <returns>True if the button is in the specified state, false if not.</returns>
        public Boolean IsButtonInState(MouseButtons button, MouseButtonStates state)
        {            
            return mButtonStates[button] == state;
        }

        /// <summary>
        /// Gets the state of a certain button.
        /// </summary>
        /// <param name="button">The button to check.</param>
        /// <returns>The state the button is in.</returns>
        public MouseButtonStates GetButtonState(MouseButtons button)
        {
            return mButtonStates[button];
        }

        /// <summary>
        /// Checks if the mouse cursor (visible or not) is currently in the game window.
        /// </summary>
        /// <returns>True if it is, false if it isn't.</returns>
        public Boolean IsInGameWindow()
        {
            return IsInBounds(new Rectangle(0, 0, Screen.Width, Screen.Height));
            //return (X >= 0 && X <= Screen.Width && Y >= 0 && Y <= Screen.Height);
        }

        /// <summary>
        /// Checks if the mouse cursor is currently within a specified area.
        /// </summary>
        /// <param name="bounds"></param>
        /// <returns></returns>
        public Boolean IsInBounds(Rectangle bounds)
        {
            return (X >= bounds.X && X <= bounds.X + bounds.Width && Y >= bounds.Y && Y <= bounds.Y + bounds.Height);                        
        }

    }
}
